package cn.zwx.feign;

import cn.zwx.constant.FeignRpcCommonConstant;
import cn.zwx.enumerate.HandleEnum;
import cn.zwx.exception.RemotingConnectException;
import cn.zwx.exception.RemotingSendRequestException;
import cn.zwx.exception.RemotingTimeoutException;
import cn.zwx.remoting.RemotingCommand;
import cn.zwx.remoting.netty.RemotingClient;
import feign.Client;
import feign.Request;
import feign.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;

import javax.annotation.Resource;
import java.net.URI;
import java.util.UUID;

import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

/**
 * @author zhangwenxue
 * <code>
 *     核心处理类
 *     这里去拦截Client请求 换成我们的Netty方式调用
 * </code>
 */
public class FeignRpcClient implements Client {
    public static final String UNDERLINE_SYMBOL = "_";

    public static final int REMOTING_RPC_TIMEOUT = 1000 * 60 * 5;

    private static final Integer ERROR_CODE_MAX = 600;

    private final static Logger logger = LoggerFactory.getLogger(FeignRpcClient.class);

    private final RemotingClient remotingClient;

    public FeignRpcClient(RemotingClient client) {
        this.remotingClient = client;
    }


    @Resource
    private LoadBalancerClient loadBalancerClient;

    @Override
    public Response execute(Request request, Request.Options options) {
        String url = request.url();
        final URI originalUri = URI.create(request.url());
        String serviceId = originalUri.getHost();
        // 负载均衡
        ServiceInstance instance = loadBalancerClient.choose(serviceId);
        String key = createKey(instance);
        String uuid = uuidKey();
        url =  url.replaceAll(serviceId,instance.getHost()+":"+instance.getPort());
        RemotingCommand requestCommand = transformRequestToRemotingCommand(request, url, uuid);
        RemotingCommand remotingCommand;
        try {
            remotingCommand = remotingClient.invokeSync(key, requestCommand, REMOTING_RPC_TIMEOUT);
            return transformRemotingCommandToResponse(remotingCommand,request);
        } catch (RemotingSendRequestException e) {
            logger.error(e.toString());
            throw new RuntimeException("远程发送失败:" + e.toString());
        } catch (RemotingTimeoutException e) {
            logger.error(e.toString());
            throw new RuntimeException("远程发送超时:" + e.toString());
        } catch (RemotingConnectException e) {
            logger.error(e.toString());
            throw new RuntimeException("远程连接异常:" + e.toString());
        } catch (Throwable throwable) {
            logger.error(throwable.toString());
            throw new RuntimeException(throwable.toString());
        }
    }

    /**
     * 将我们之前封装好的 Request转换为 RemotingCommand
     * @param request 请求参数
     * @param url url
     * @param uuid 请求Id
     * @return  RemotingCommand
     */
    private RemotingCommand transformRequestToRemotingCommand(Request request, String url, String uuid) {
        RemotingCommand requestCommand = new RemotingCommand();
        requestCommand.setXid(uuid);
        requestCommand.setBody(request.body());
        requestCommand.setMethod(request.requestTemplate().method());
        requestCommand.setUrl(url);
        requestCommand.setHeader(request.headers());
        requestCommand.setType(HandleEnum.REQUEST_COMMAND.getCode());
        return requestCommand;
    }

    /**
     * 将我们之前封装好的 RemotingCommand 转换为 Response
     * @param remotingCommand RemotingCommand
     * @param request 这个对象必须用原来的 不然会报错 original request is required
     * @return Response
     */
    private Response transformRemotingCommandToResponse(RemotingCommand remotingCommand,Request request) {
        if (null == remotingCommand) {
            logger.error("not accept response result");
            throw new RuntimeException("not accept response result");
        }
        if (remotingCommand.getCode() >= SC_INTERNAL_SERVER_ERROR && remotingCommand.getCode() < ERROR_CODE_MAX) {
            logger.error("remote invoke error:{}", remotingCommand.getRemark());
            throw new RuntimeException(remotingCommand.getError());
        }
        Response.Builder builder = Response.builder().status(remotingCommand.getCode()).body(remotingCommand.getBody())
                .request(request);
        builder.headers(remotingCommand.getHeader());
        return builder.build();
    }

    /**
     * 根据uri构建唯一key
     * @param serviceInstance serviceInstance
     * @return String
     */
    private String createKey(ServiceInstance serviceInstance) {
        int port = serviceInstance.getPort() + FeignRpcCommonConstant.STEP;
        return serviceInstance.getHost() + UNDERLINE_SYMBOL + port;
    }


    /**
     * 构建唯一ID，用于维持当前调用链
     * @return String
     */
    private String uuidKey() {
        return UUID.randomUUID().toString();
    }


}
